﻿#region Header
//
//   Project:           FaceLight - Simple Silverlight Real Time Face Detection.
//
//   Changed by:        $Author$
//   Changed on:        $Date$
//   Changed in:        $Revision$
//   Project:           $URL$
//   Id:                $Id$
//
//
//   Copyright (c) 2010 Rene Schulte
//
//   This program is open source software. Please read the License.txt.
//
#endregion

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace FaceLight
{
   public partial class MainPage : UserControl
   {
      CaptureSource captureSource;
      IFilter erodeFilter;
      IFilter dilateFilter;
      HistogramMinMaxSegmentator segmentator;
      HistogramVisualizer histogramViz;
      CbCrColorSpaceVisualizer colorSpaceViz;
      WriteableBitmap loadedPicture;

      public ColorRangeFilter SkinColorFilter { get; set; }

      public MainPage()
      {
         InitializeComponent();
      }

      private void Init()
      {
         try
         {
            // Init filters and segmentation
            SkinColorFilter = new ColorRangeFilter
            {
               LowerThreshold = new YCbCrColor(0.10f, -0.15f, 0.05f),
               UpperThreshold = new YCbCrColor(1.00f,  0.05f, 0.20f),
            };
            erodeFilter = new Erode5x5Filter();
            dilateFilter = new Dilate5x5Filter();
            segmentator = new HistogramMinMaxSegmentator();

            // Init Visualizers
            histogramViz = new HistogramVisualizer { Scale = 20 };
            colorSpaceViz = new CbCrColorSpaceVisualizer();

            // Init capture source
            captureSource = new CaptureSource();

            try
            {
               captureSource.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
               captureSource.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();
               captureSource.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(captureSource_CaptureImageCompleted);

               // Desired format is 320 x 240 (good tracking results and performance)
               captureSource.VideoCaptureDevice.DesiredFormat = new VideoFormat(PixelFormatType.Unknown, 320, 240, 30);
            }
            catch (Exception ex)
            {
               MessageBox.Show("Webcam could not be found. Please make sure a default webcam is set.\r\n" + ex.Message, "No webcam", MessageBoxButton.OK);
               BtnCapture.IsEnabled = false;
            }
     
            // Init Data Context
            this.DataContext = this;
            
            // And rest
            UpdateColorSpaceImage();
            SetOverlayObject();

            // Start all
            RunUpdate();
         }
         catch (Exception ex)
         {
            MessageBox.Show("Error during init." + ex.Message, "Error during init.", MessageBoxButton.OK);
            throw;
         }
      }

      private void RunUpdate()
      {
         var dispatcherTimer = new DispatcherTimer();
         dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 40); // 25 fps
         dispatcherTimer.Tick += (s, e) =>
         {
            // Process camera snapshot if started or a loaded pic if the cam was not started
            if (captureSource.State == CaptureState.Started)
            {
               captureSource.CaptureImageAsync();
            }
            else
            {
               Process(loadedPicture);
            }
         };
         dispatcherTimer.Start();
      }

      private void ActivateLoadedPicture()
      {
         // Stop capturing if running
         if (captureSource != null && captureSource.State == CaptureState.Started)
         {
            StartStopCapture();
         }

         WebcamVideo.Fill = new ImageBrush { ImageSource = loadedPicture };
         Process(loadedPicture);
      }

      private void Process(WriteableBitmap bmp)
      {
         if (bmp == null)
         {
            return;
         }

         // Filter
         var skin = SkinColorFilter.Process(bmp);
         var erode = erodeFilter.Process(skin);
         var dilate = dilateFilter.Process(erode);
         dilate = dilateFilter.Process(dilate);
         dilate = dilateFilter.Process(dilate);

         // Segment
         var histogram = Histogram.FromWriteabelBitmap(dilate);
         segmentator.Histogram = histogram;
         segmentator.ThresholdLuminance = histogram.Max * 0.1f;
         var foundSegments = segmentator.Process(dilate);

         // Visualize
         histogramViz.Histogram = histogram;
         histogramViz.Visualize(dilate);
         Overlay(foundSegments, bmp.PixelWidth, bmp.PixelHeight);

         // Present
         ImgSkin.Source = skin;
         ImgDilate.Source = dilate;
      }

      private void Overlay(System.Collections.Generic.IEnumerable<Segment> foundSegments, int w, int h)
      {
         // Highlight the found segments with a red ellipse
         var result = new WriteableBitmap(w, h);
         foreach (var s in foundSegments)
         {
            // Uses the segment's center and half width, height
            var c = s.Center;
            result.DrawEllipseCentered(c.X, c.Y, s.Width >> 1, s.Height >> 1, Colors.Red);
         }
         ImgResult.Source = result;
         
         // If an image should be overlaid
         if (Combo.SelectedIndex == 0)
         {
            if (foundSegments.Any())
            {
               TransformOverlaidImage(foundSegments, w, h);
            }
         }
      }

      private void TransformOverlaidImage(System.Collections.Generic.IEnumerable<Segment> foundSegments, int w, int h)
      {
         // Set width and height of image by using the first segment's information
         var s = foundSegments.First();
         var iw = s.Width;
         var ih = s.Height;
         ImgOverlay.Width = iw;
         ImgOverlay.Height = ih;

         // Create the transform to keep the image's size and position in sync with the facial region
         var transform = new TransformGroup();
         
         // Scale image and move it to the segment's position
         transform.Children.Add(new ScaleTransform { ScaleX = 1.5, ScaleY = 1.5, CenterX = iw >> 1, CenterY = ih >> 1 });
         transform.Children.Add(new TranslateTransform { X = s.Min.X, Y = s.Min.Y + 10 });

         // Calcualte scale from Bitmap to actual size and create the transformation
         var sx = (GrdContent.ActualWidth / w);
         var sy = (GrdContent.ActualHeight / h);
         transform.Children.Add(new ScaleTransform { ScaleX = sx, ScaleY = sy });
         
         // Apply transformation
         ImgOverlay.RenderTransform = transform;
      }

      private void UpdateColorSpaceImage()
      {
         // Redraw the color space image using the CbCrColorSpaceVisualizer
         var colorSpace = new WriteableBitmap((int)ImgPalette.Width, (int)ImgPalette.Height);
         colorSpaceViz.Min = SkinColorFilter.LowerThreshold;
         colorSpaceViz.Max = SkinColorFilter.UpperThreshold;
         colorSpaceViz.Visualize(colorSpace);
         ImgPalette.Source = colorSpace;
      }

      private void StartStopCapture()
      {
         try
         {
            // Start capturing
            if (captureSource.State != CaptureState.Started)
            {
               captureSource.Stop();

               // Create video brush and fill the rectangle with it
               var vidBrush = new VideoBrush();
               vidBrush.Stretch = Stretch.Uniform;
               vidBrush.SetSource(captureSource);
               WebcamVideo.Fill = vidBrush;
               segmentator.Reset();

               // Ask user for permission
               if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
               {
                  captureSource.Start();
               }
            }
            else
            {
               // Stop capturing
               captureSource.Stop();
            }
         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.Message, "Error using webcam", MessageBoxButton.OK);
            throw;
         }
      }

      private void SetOverlayObject()
      {
         if (this.ImgResult != null && this.ImgOverlay != null && this.TxtImageSource != null)
         {
            this.ImgResult.Visibility = System.Windows.Visibility.Collapsed;
            this.ImgOverlay.Visibility = System.Windows.Visibility.Collapsed;
            this.TxtImageSource.Visibility = System.Windows.Visibility.Collapsed;
         }

         if (Combo != null && Combo.SelectedIndex != -1)
         {
            switch (Combo.SelectedIndex)
            {
               case 0:
                  this.ImgOverlay.Visibility = System.Windows.Visibility.Visible;
                  this.TxtImageSource.Visibility = System.Windows.Visibility.Visible;
                  break;
               case 1:
                  this.ImgResult.Visibility = System.Windows.Visibility.Visible;
                  break;
               default:
                  break;
            }
         }
      }



      private void UserControl_Loaded(object sender, RoutedEventArgs e)
      {
         Init();
      }

      private void captureSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
      {
         // Process camera snapshot
         Process(e.Result);
      }

      private void BtnCapture_Click(object sender, RoutedEventArgs e)
      {
         StartStopCapture();
      }

      private void BtnOpenPic_Click(object sender, RoutedEventArgs e)
      {
         // OpenFileDialog.ShowDialog() can only be called from user-initiated code 
         // like an event handler, otherwise a SecurityException is thrown.
         var openFileDialog = new OpenFileDialog();
         if (openFileDialog.ShowDialog().Value)
         {
            // Open the stream and load the image
            using (var stream = openFileDialog.File.OpenRead())
            {
               // Fill WriteableBitmap from stream
               var bmpImg = new BitmapImage();
               bmpImg.SetSource(stream);
               loadedPicture = new WriteableBitmap(bmpImg);

               // Resize the image if it’s too large
               var w = (int)GrdContent.Width;
               var h = (int)GrdContent.Height;
               if (loadedPicture.PixelWidth > w || loadedPicture.PixelHeight > h)
               {
                  loadedPicture = loadedPicture.Resize(w, h, WriteableBitmapExtensions.Interpolation.Bilinear);
               }

               ActivateLoadedPicture();
            }
         }
      }

      private void BtnSnapshot_Click(object sender, RoutedEventArgs e)
      {
         // Render
         var wb = new WriteableBitmap(GrdContent, null);

         // Init SaveFileDialog
         var saveFileDlg = new SaveFileDialog
         {
            DefaultExt = ".tga",
            Filter = "TGA Image (*tga)|*.tga",
         };

         // SaveFileDialog.ShowDialog() can only be called from user-initiated code 
         // like an event handler, otherwise a SecurityException is thrown.
         if (saveFileDlg.ShowDialog().Value)
         {
            using (var dstStream = saveFileDlg.OpenFile())
            {
               wb.WriteTga(dstStream);
            }
         }
      }

      private void SldYCbCr_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
      {
         UpdateColorSpaceImage();
      }

      private void Combo_SelectionChanged(object sender, SelectionChangedEventArgs e)
      {
         SetOverlayObject();
      }
   }
}
